安装依赖
pnpm add @nestjs/mongoose mongoose
bash
| 包名 | 作用 |
|---|---|
@nestjs/mongoose | NestJS Mongoose 集成模块,提供装饰器和 DI 支持 |
mongoose | MongoDB ODM 核心 |
Docker 启动 MongoDB
# docker-compose-mongo.yml
services:
mongo:
image: mongo
ports:
- "27017:27017"
environment:
MONGO_INITDB_ROOT_USERNAME: root
MONGO_INITDB_ROOT_PASSWORD: example
mongo-express:
image: mongo-express
ports:
- "8081:8081"
environment:
ME_CONFIG_MONGODB_ADMINUSERNAME: root
ME_CONFIG_MONGODB_ADMINPASSWORD: example
ME_CONFIG_MONGODB_URL: mongodb://root:example@mongo:27017/
yaml
启动:
docker compose -f docker-compose-mongo.yml up -d
bash
- MongoDB 端口 27017
- Mongo Express 端口 8081(可视化管理工具,类似 MySQL 的 Adminer)
模块配置
forRoot 连接数据库
// app.module.ts
import { MongooseModule } from '@nestjs/mongoose';
@Module({
imports: [
MongooseModule.forRoot('mongodb://root:example@localhost:27017/nest'),
],
})
export class AppModule {}
typescript
连接字符串格式:
mongodb://username:password@host:port/database
text
数据库用户权限
使用 Docker 启动的 MongoDB 默认只有管理员账号。新建的数据库需要单独授权:
# 进入 MongoDB CLI
docker exec -it <container_name> mongosh -u root -p
# 切换到目标数据库
use nest
# 创建专用用户
db.createUser({
user: 'root',
pwd: 'example',
roles: [{ role: 'dbOwner', db: 'nest' }]
})
bash
如果不创建用户,NestJS 连接时会报认证失败错误。
Schema 定义(两种方式)
方式一:装饰器(@Schema + @Prop)
这是 @nestjs/mongoose 推荐的方式,代码语义更贴近 NestJS 风格:
// user/user.schema.ts
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { Document } from 'mongoose';
@Schema({ collection: 'users' })
export class User extends Document {
@Prop({ required: true })
username: string;
@Prop({ required: true })
password: string;
}
export const UserSchema = SchemaFactory.createForClass(User);
typescript
| 装饰器/方法 | 说明 |
|---|---|
@Schema({ collection: 'users' }) | 标记为 Mongoose Schema,指定集合名 |
@Prop({ required: true }) | 定义字段属性 |
SchemaFactory.createForClass(User) | 从类生成 Mongoose Schema 实例 |
方式二:原生 Schema 对象
直接使用 Mongoose 原生的 Schema 构造函数:
// user/user.schema.ts
import * as mongoose from 'mongoose';
export const UserSchema = new mongoose.Schema(
{
username: { type: String, required: true },
password: { type: String, required: true },
},
{ collection: 'users' },
);
typescript
两种方式对比
| 维度 | 装饰器方式 | 原生 Schema |
|---|---|---|
| 代码量 | 稍多(需要 Document 类型 + SchemaFactory) | 更简洁(2-3 行定义完成) |
| 可读性 | NestJS 风格,语义化更强 | Mongoose 原生风格 |
| 类型安全 | TypeScript 自动推导 | 需手动定义接口 |
| 维护性 | 类名与 forFeature 自动关联 | 集合名通过字符串对应,需常量文件管理 |
| 健壮性 | 重构时 IDE 可自动追踪 | 字符串关联,重构需手动修改 |
@nestjs/mongoose 官方推荐装饰器方式的原因是减少了样板代码并提高了整体可读性。但实际选择取决于团队偏好——如果团队已经熟悉 Mongoose 原生写法,Schema 对象方式同样有效。
forFeature 注册 Schema
// app.module.ts
import { MongooseModule } from '@nestjs/mongoose';
import { User, UserSchema } from './user/user.schema';
@Module({
imports: [
// ... forRoot 配置
MongooseModule.forFeature([
{ name: User.name, schema: UserSchema },
]),
],
})
export class AppModule {}
typescript
forFeature 将 Schema 注册到 NestJS 的 DI 系统中,使得可以通过 @InjectModel() 注入 Model 实例。
Model 注入与使用
// app.controller.ts
import { Controller, Get } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { User } from './user/user.schema';
@Controller()
export class AppController {
constructor(
@InjectModel(User.name) private readonly userModel: Model<User>,
) {}
@Get()
async findAll(): Promise<User[]> {
return this.userModel.find().exec();
}
}
typescript
@InjectModel(User.name) 是 NestJS 提供的注入装饰器,注入的 userModel 拥有 Mongoose Model 的全部方法:
| 方法 | 说明 |
|---|---|
find() | 查询所有文档 |
findOne({ username }) | 条件查询 |
findById(id) | 按 ID 查询 |
create(doc) | 创建文档 |
findByIdAndUpdate(id, update) | 更新 |
findByIdAndDelete(id) | 删除 |
aggregate(pipeline) | 聚合管道查询 |
MongoDB 管理操作
在 Mongo Express(localhost:8081)中可以手动管理数据:
- 创建数据库 → 点击 "Create Database" → 输入名称
nest - 创建集合 → 进入数据库 → 点击 "Create Collection" → 输入
users - 插入文档 → 进入集合 → 点击 "New Document" → 输入 JSON:
{
"username": "tom",
"password": "123456"
}
json
MongoDB 会自动生成 _id(ObjectId)字段。
@nestjs/mongoose 源码分析
核心架构
@nestjs/mongoose 包的设计遵循 NestJS 模块化思想,核心由三部分组成:
@nestjs/mongoose
├── decorators/
│ ├── @Schema() → 收集元数据到类
│ └── @Prop() → 收集字段元数据
├── mongoose-core.module.ts → forRoot:创建 MongoDB 连接
└── mongoose.providers.ts → createMongooseProviders:为 forFeature 创建 DI Provider
text
forRoot 工作原理
MongooseModule.forRoot() 的核心逻辑只有几行:
// mongoose-core.module.ts(简化)
const connection = await mongoose.createConnection(uri);
return connection;
typescript
它调用 Mongoose 的 createConnection 创建连接实例,并注册为 Provider。
可选参数 lazyConnection 控制是否延迟创建连接——仅在首次使用时才建立连接,适合启动速度敏感的场景。
forFeature 工作原理
forFeature 的核心逻辑:
1. 接收 Schema 定义(如 { name: User.name, schema: UserSchema })
2. 调用 createMongooseProviders()
3. 从连接实例中获取对应 Model(connection.model(name, schema))
4. 将 Model 注册为 DI Provider
5. Controller 通过 @InjectModel() 注入使用
text
最终 userModel.find() 调用的是原生 Mongoose Model 方法,@nestjs/mongoose 只是做了一层 DI 包装。
@Schema 装饰器的元数据流
@Schema() → addSchemaMetadata(target, options)
→ 存入 TypeMetadataStorage
→ forFeature 时读取并传递给 SchemaFactory.createForClass()
→ 生成 Mongoose Schema 实例
text
这种元数据驱动的模式是 NestJS 的核心设计思想——装饰器只收集信息,模块在初始化时消费这些信息。
完整工作流总结
1. pnpm add @nestjs/mongoose mongoose → 安装依赖
2. Docker 启动 MongoDB → 数据库就绪
3. MongooseModule.forRoot(uri) → 建立连接
4. @Schema() + @Prop() 定义 Schema → 数据模型
5. MongooseModule.forFeature([...]) → 注册 Schema
6. @InjectModel() 注入 Model → 业务使用
7. Mongo Express 手动管理数据 → 可视化验证
text
三套 ORM 集成(Prisma + TypeORM + Mongoose)至此全部完成。下一节将讲解动态模块的工作原理,理解 forRoot、forRootAsync、forFeature 背后的设计思想。
↑